#include "util/util.h"
#include <assert.h>
using namespace util;
const int SCR_W = 640;
const int SCR_H = 480;
const float LIGHT_AMB[4] = { 0.7f, 0.7f, 0.7f, 1.0f };
const float LIGHT_DIF[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
const float LIGHT_POS[4] = { 4.0f, 4.0f, 6.0f, 1.0f };
CCamera cam;
CGeometry sphere, torus;
float objx[2] = { -10.0f, 10.0f };
float objz[2] = { -3.0f,  3.0f };

void init()
{
	sphere.create_sphere(64);
	torus.create_torus(48);
	
	glViewport(0, 0, SCR_W, SCR_H);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepth(1.0);
	glClearStencil(0);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_NORMALIZE);
	
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	cam.perspective(45.0f, (float)SCR_W/SCR_H, 0.1f, 1000.f);
	
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightfv(GL_LIGHT0, GL_AMBIENT, LIGHT_AMB);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LIGHT_DIF);
	glLightfv(GL_LIGHT0, GL_POSITION, LIGHT_POS);
}
void frame()
{

}
void render_floor()
{
	static float FLOOR_POS[] = { -2,0,2, -2,0,-2, 2,0,-2, 2,0,2 };
	static float FLOOR_NOR[] = { 0,1,0,  0,1,0,   0,1,0,  0,1,0 };
	glVertexPointer(3, GL_FLOAT, 0, FLOOR_POS);
	glNormalPointer(GL_FLOAT, 0, FLOOR_NOR);
	glDrawArrays(GL_QUADS, 0, 12);
}
void render_model(const CGeometry& o)
{
	glVertexPointer(3, GL_FLOAT, 0, o.get_vertex());
	glNormalPointer(GL_FLOAT, 0, o.get_normal());
	glDrawElements(GL_TRIANGLES, o.get_index_num(), GL_UNSIGNED_SHORT, o.get_index_data());
}
void setup_model_material(int i)
{
	color adc;
	if (0 == i)   adc = c4gen(0.2f, 0.0f, 0.0f, 1.0f);
	if (1 == i)   adc = c4gen(0.0f, 0.2f, 0.0f, 1.0f);
	if (2 == i)   adc = c4gen(0.2f, 0.2f, 0.0f, 1.0f);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, adc.c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c4gen(0.3f, 0.3f, 0.3f, 1.0f).c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, c4gen(0,0,0,0).c);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 128.0f);
}
void setup_floor_material()
{
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, c4gen(0.8f, 0.8f, 0.8f, 1.0f).c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, c4gen(0.24f, 0.45f, 0.68f, 1.0f).c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c4gen(1,1,1,1).c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, c4gen(1,1,1,1).c);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 128.0f);
}
void render()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	cam.lookat(0.0f, 5.0f, 3.0f,
			   0.0f, 0.0f, 0.0f,
			   0.0f, 1.0f, 0.0f);
	
	/*
		disable write frame buffer color since only need render in stencil buffer
	*/
	glColorMask(false, false, false, false);
	/*
		render floor in stencil buffer
		the floor region's stencil buffer will be 1
	*/
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_ALWAYS, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	glDisable(GL_DEPTH_TEST);
	render_floor();
	/*
		render models under floor as reflection
		only render when stencil buffer is 1 , keep render region only floor
		enable clip plane0 , so the models only render under plane y = -1
	*/
	glEnable(GL_DEPTH_TEST);
	glColorMask(true, true, true, true);
	glStencilFunc(GL_EQUAL, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	static double eqr[4] = { 0.0, -1.0, 0.0, 0.0 };
	glClipPlane(GL_CLIP_PLANE0, eqr);
	glEnable(GL_CLIP_PLANE0);
	
	static const float BALL_SCALE = 0.3f;
	static const float BALL_Y = 3.0f;
	static const float TORUS_SCALE = 0.1f;
	static const float TORUS_Y = 10.0f;
	static float torus_ang = 0;
	for (int i=0; i<2; i++)
	{
		setup_model_material(i);
		glPushMatrix();
			glScalef(BALL_SCALE, -BALL_SCALE, BALL_SCALE);
			glLightfv(GL_LIGHT0, GL_POSITION, LIGHT_POS);
			glTranslatef(objx[i], BALL_Y, objz[i]);
			render_model(sphere);
		glPopMatrix();
	}
	setup_model_material(2);
	glPushMatrix();
		glScalef(TORUS_SCALE, -TORUS_SCALE, TORUS_SCALE);
		glLightfv(GL_LIGHT0, GL_POSITION, LIGHT_POS);
		glTranslatef(0.0f, TORUS_Y, 0.0f);
		glRotatef(torus_ang, 1, 1, 1);
		render_model(torus);
	glPopMatrix();
	
	glDisable(GL_CLIP_PLANE0);
	glDisable(GL_STENCIL_TEST);
	
	/*
		render transparent floor
	*/
	glLightfv(GL_LIGHT0, GL_POSITION, LIGHT_POS);
	glEnable(GL_BLEND);
	glDisable(GL_LIGHTING);
	glColor4f(1.0f, 1.0f, 1.0f, 0.6f);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	setup_floor_material();
	render_floor();
	glEnable(GL_LIGHTING);
	glDisable(GL_BLEND);
	
	/*
		render models
	*/
	for (int i=0; i<2; i++)
	{
		setup_model_material(i);
		glPushMatrix();
			glScalef(BALL_SCALE, BALL_SCALE, BALL_SCALE);
			glTranslatef(objx[i], BALL_Y, objz[i]);
			render_model(sphere);
		glPopMatrix();
	}
	setup_model_material(2);
	glPushMatrix();
		glScalef(TORUS_SCALE, TORUS_SCALE, TORUS_SCALE);
		glTranslatef(0.0f, TORUS_Y, 0.0f);
		glRotatef(torus_ang, 1, 1, 1);
		render_model(torus);
	glPopMatrix();
	
	objx[0] += 0.05f;
	objx[1] -= 0.05f;
	const float SIDE = 12.0f;
	if (objx[0] < -SIDE)  objx[0] = SIDE;
	if (objx[1] < -SIDE)  objx[1] = SIDE;
	if (objx[0] > SIDE)   objx[0] = -SIDE;
	if (objx[1] > SIDE)   objx[1] = -SIDE;
	
	torus_ang += 2;
}

int main()
{
	g_p_system = new CSystem(SCR_W, SCR_H, "Reflection by qq.d.y");
	g_p_system->set_callback(CSystem::INIT, init);
	g_p_system->set_callback(CSystem::RENDER, render);
	g_p_system->set_callback(CSystem::FRAME, frame);
	g_p_system->run();
	return 0;
}